package jsu.weizixuan.likesystem.service.impl;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONString;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.benmanes.caffeine.cache.Cache;
import jakarta.servlet.http.HttpServletRequest;
import jsu.weizixuan.likesystem.constant.MQConstant;
import jsu.weizixuan.likesystem.constant.RedisLuaScriptConstant;
import jsu.weizixuan.likesystem.constant.ThumbConstant;
import jsu.weizixuan.likesystem.job.RetryableEventSender;
import jsu.weizixuan.likesystem.manager.CacheManager;
import jsu.weizixuan.likesystem.mapper.ThumbMapper;
import jsu.weizixuan.likesystem.model.enums.LuaStatusEnum;
import jsu.weizixuan.likesystem.model.msg.ThumbEvent;
import jsu.weizixuan.likesystem.model.pojo.DoThumbRequest;
import jsu.weizixuan.likesystem.model.pojo.Thumb;
import jsu.weizixuan.likesystem.model.pojo.User;
import jsu.weizixuan.likesystem.model.vo.BlogVO;
import jsu.weizixuan.likesystem.service.BlogService;
import jsu.weizixuan.likesystem.service.ThumbService;
import jsu.weizixuan.likesystem.service.UserService;
import jsu.weizixuan.likesystem.util.RedisKeyUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;


@Service("thumbService")
@Slf4j
@RequiredArgsConstructor
public class ThumbServiceMQImpl extends ServiceImpl<ThumbMapper, Thumb> implements ThumbService {

    private final UserService userService;

    private final RedisTemplate<String, Object> redisTemplate;

    private final BlogService blogService;

    private final RetryableEventSender retryableEventSender;

    private final CacheManager cacheManager;

    private final Cache<String, Object> localCache;

    /**
     * 判断用户是否已点赞
     *
     * @param blogId
     * @param userId
     * @return
     */
    @Override
    public Boolean hasThumb(Long blogId, Long userId) {
        return redisTemplate.opsForHash().hasKey(ThumbConstant.USER_THUMB_KEY_PREFIX + userId, blogId.toString());
    }

    /**
 * 获取点赞数
 *
 * @param blogId 博客ID
 * @param request HTTP请求对象
 * @return 点赞数量
 */
@Override
public Long getThumbCount(Long blogId, HttpServletRequest request) {
    String thumbCountKey = RedisKeyUtil.getThumbCountKey(blogId);
    Object result = redisTemplate.opsForValue().get(thumbCountKey);

    // 如果Redis中没有缓存该博客的点赞数
    if (result == null) {
        try {
            // 获取博客信息以检查是否是热博客
            BlogVO blogVO = blogService.getBlogVOById(blogId, request);
            if (blogVO != null) {
                Date createTime = blogVO.getCreateTime();
                // 判断当前博客是否是热博客（发布日期在一个月内）
                DateTime oneMonthAgo = DateUtil.offsetMonth(new Date(), -1);
                Long thumbCount = Long.valueOf(blogVO.getThumbCount());
                if (createTime.after(oneMonthAgo)) {
                    // 是热博客，从数据库加载初始点赞数并放入Redis缓存
                    // 设置过期时间，例如24小时
                    redisTemplate.opsForValue().set(thumbCountKey, thumbCount, Duration.ofHours(24));
                }else{
                    // 设置过期时间，例如2小时
                    redisTemplate.opsForValue().set(thumbCountKey, thumbCount, Duration.ofHours(2));
                }
                return thumbCount;
            }
        } catch (Exception e) {
            log.error("获取博客点赞数失败，blogId: {}", blogId, e);
        }
        // 默认返回0
        return 0L;
    }

    return (Long) result;
}


    /**
     * 版本二：
     * 通过lua脚本实现原子性操作，保证点赞状态和临时记录要么同时存在，要么同时不存在，不会因为系统宕机而导致数据不一致
     */
    /**
     * 点赞
     *
     * @param doThumbRequest
     * @param request
     * @return
     */
    @Override
    public Boolean doThumb(DoThumbRequest doThumbRequest, HttpServletRequest request) {
        if (doThumbRequest == null || doThumbRequest.getBlogId() == null) {
            throw new RuntimeException("参数错误");
        }
        User loginUser = userService.getLoginUser(request);
        Long loginUserId = loginUser.getId();
        Long blogId = doThumbRequest.getBlogId();

        String userThumbKey = RedisKeyUtil.getUserThumbKey(loginUserId);
        String thumbCountKey = RedisKeyUtil.getThumbCountKey(blogId);
        getThumbCount(blogId, request);
        // 如果是热点文章，先从caffeine本地缓存查看点赞情况，再从Redis中获取
        Object value = cacheManager.get(
                userThumbKey,
                blogId.toString(),

                (hashkey, key)->{
                    // 构造唯一的 composite key
                    String compositeKey = CacheManager.buildCacheKey(hashkey, key);
                    Object result = localCache.getIfPresent(compositeKey);
                    if (result != null&&!result.equals(ThumbConstant.UN_THUMB_CONSTANT)) {
                        log.info("本地缓存获取到数据 {} = {}", compositeKey, result);
                        return null;
                    }
                    return result;
                },
                (hashkey, key) -> {
                    Long result = redisTemplate.execute(
                            RedisLuaScriptConstant.THUMB_SCRIPT_MQ,
                            List.of(hashkey,thumbCountKey,ThumbConstant.THUMB_BLOG_KEY_PREFIX),
                            key,
                            System.currentTimeMillis()
                    );
                    if (result == LuaStatusEnum.FAIL.getValue()) {
                        return null;
                    }
                    return result;
                });

        if (value == null) {
            throw new RuntimeException("用户已点赞");
        }

        // 发送的消息体
        ThumbEvent thumbEvent = ThumbEvent.builder()
                .userId(loginUser.getId())
                .blogId(blogId)
                .type(ThumbEvent.EventType.INCR)
                .eventTime(LocalDateTime.now())
                .build();
        // 通过带重试的异步发送器，发送消息（如果消息发送失败，通过retry实现消息重试）
        retryableEventSender.sendAsync(MQConstant.THUMB_EXCHANGE, MQConstant.THUMB_ROUTING_KEY, JSON.toJSONString(thumbEvent));
//        rabbitTemplate.convertAndSend(MQConstant.THUMB_EXCHANGE, MQConstant.THUMB_ROUTING_KEY, JSON.toJSONString(thumbEvent));
        // 如果是热点博客，则更新caffeine的点赞状态
        cacheManager.putIfPresent(userThumbKey, blogId.toString(), blogId);
        // 更新成功
        return true;
    }

    /**
     * 取消点赞
     *
     * @param doThumbRequest
     * @param request
     * @return
     */
    @Override
    public Boolean undoThumb(DoThumbRequest doThumbRequest, HttpServletRequest request) {
        if (doThumbRequest == null || doThumbRequest.getBlogId() == null) {
            throw new RuntimeException("参数错误");
        }
        User loginUser = userService.getLoginUser(request);
        Long loginUserId = loginUser.getId();
        Long blogId = doThumbRequest.getBlogId();
        String userThumbKey = RedisKeyUtil.getUserThumbKey(loginUserId);
        // 如果是热点文章，先从caffeine本地缓存查看点赞情况，再从Redis中获取

        Object value = cacheManager.get(
                userThumbKey,
                blogId.toString(),
                (hashkey, key)->{
                    // 构造唯一的 composite key
                    String compositeKey = CacheManager.buildCacheKey(hashkey, key);
                    Object result = localCache.getIfPresent(compositeKey);
                    if (result == null || result.equals(ThumbConstant.UN_THUMB_CONSTANT)) {
                        return null;
                    }
                    return result;
                },
                (hashkey, key) -> {
                    // 执行 Lua 脚本，点赞记录从 Redis 删除
                    Long result = redisTemplate.execute(
                            RedisLuaScriptConstant.UNTHUMB_SCRIPT_MQ,
                            List.of(userThumbKey),
                            blogId
                    );
                    if (result == LuaStatusEnum.FAIL.getValue()) {
                        return null;
                    }
                    return result;
                });

        if (value == null) {
            throw new RuntimeException("用户未点赞");
        }
        ThumbEvent thumbEvent = ThumbEvent.builder()
                .blogId(blogId)
                .userId(loginUserId)
                .type(ThumbEvent.EventType.DECR)
                .eventTime(LocalDateTime.now())
                .build();

        // 通过带重试的异步发送器，发送消息（如果消息发送失败，通过retry实现消息重试）
        retryableEventSender.sendAsync(MQConstant.THUMB_EXCHANGE, MQConstant.THUMB_ROUTING_KEY, JSON.toJSONString(thumbEvent));
        // 如果是热点博客，则更新caffeine的点赞状态
        cacheManager.putIfPresent(userThumbKey, blogId.toString(),ThumbConstant.UN_THUMB_CONSTANT);
        // 更新成功
        return true;

    }

}
